#include"definicje.h"
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netdb.h>
#include<unistd.h>
#include<unistd.h>
#include"protokol.h"
#include"adres.h"
#include"ssl.h"
#include<openssl/ssl.h>
#include<openssl/bio.h>
#include<openssl/err.h>


/* lista serwerów */
struct adres adresy[1]=
{
   {"localhost","9999"},
};

/* bufor do przechowywania pakietów */
int8_t bufor[MAX_PAKIET];
struct naglowek *nag=(struct naglowek *)bufor;
int8_t *dane=bufor+rozmiarNaglowka;

/* bufor na potwierdzenia */
int8_t bufor2[MAX_PAKIET];
struct pakietACK *ack=(struct pakietACK *)bufor2;
int8_t *dane2=bufor2+rozmiarACK;

/* Tworzy gniazdo i podaje adres */
void stworz(int *g, struct sockaddr_storage * s)
{
   unsigned int i;
   int wynik;
   struct addrinfo wskazowki;
   struct addrinfo *wyniki, *wskaznik;
   int gniazdo;
   bool utworzono;

   memset(&wskazowki, 0, sizeof(struct addrinfo));
   wskazowki.ai_family = AF_INET;
   wskazowki.ai_socktype = SOCK_DGRAM;
   utworzono=false;
   while(!utworzono)
   {
      for(i=0; (i<sizeof(adresy)/sizeof(struct adres)) && ! utworzono; ++i)
      {
	 wynik=getaddrinfo(adresy[i].serwer,adresy[i].port,&wskazowki,&wyniki);
	 if(wynik!=0)
	 {
	    fprintf(stderr, "getaddrinfo: %s\n",gai_strerror(wynik));
	    continue;
	 }
	 for(wskaznik = wyniki; (wskaznik != NULL) && ! utworzono; wskaznik=wskaznik->ai_next)
	 {
	    /*** Tu otwierane gniazdo - trzeba zamknąć ***/
	    gniazdo=socket(
		  wskaznik->ai_family,
		  wskaznik->ai_socktype,
		  wskaznik->ai_protocol
		  );
	    if(gniazdo==-1)
	    {
	       perror("socket");
	       continue;
	    }
	    utworzono=true;
	    memcpy(s,wskaznik->ai_addr,wskaznik->ai_addrlen);
	    *g=gniazdo;
	 }
	 freeaddrinfo(wyniki);
      }
   }
}

int main(int argc, char *argv[])
{

   int gniazdo;
   uint32_t ktory=0;
   uint32_t ktoryTam=0;
   uint16_t licznik;
   size_t r;
   int wynik;
   FILE *plik;
   struct sockaddr_storage adres;

    SSL_library_init();
    SSL_load_error_strings();

   memset(&adres,0u,sizeof(struct sockaddr_storage));
glownapetla:
#ifdef DEBUG
   fprintf(stderr,"Wchodzę do głównej pętli.\n");
#endif
      stworz(&gniazdo,&adres);

    BIO* bio = BIO_new_dgram(gniazdo,BIO_NOCLOSE);
    if (bio==NULL)
    {
        fprintf(stderr,"blad tworzenia bio");
        return 1;

    }

    int err = BIO_dgram_set_peer(bio,&adres);
    fprintf(stderr, "BIO_dgram_set_peer: %d\n", err);

    SSL_CTX *ctx = SSL_CTX_new(DTLSv1_client_method());
    SSL_CTX_set_read_ahead(ctx,1);
    SSL_CTX_set_cipher_list(ctx,"HIGH:MEDIUM:aNULL");

    SSL *ssl = SSL_new(ctx);
    if(ssl == NULL){
        fprintf(stderr,"blad tworzenia ssl");
    }

    //SSL_set_bio(ssl,bio,bio);
    //SSL_set_connect_state(ssl);
/*
    char *c = "Hello, enciphered World!\n";
	err = SSL_write(ssl, c, strlen(c));
	if (err <= 0) {
		err = SSL_get_error(ssl, err);
        fprintf(stderr, "SSL_write: error %d\n", err);
	}

	SSL_free(ssl); /* free conn too
	SSL_CTX_free(ctx);*/



      nag->rozmiar=htons((int16_t)2u);
      nag->operacja=OP_SET;
      nag->opcje=(int8_t)0u;
      nag->ktory=htonl(ktory);
      {
	 int16_t *dd=(int16_t *)dane;
	 *dd=htons((uint16_t)4u);
      }

      wynik=wyslijzpotwSSL(gniazdo,&adres,bufor,bufor2,&ktory,&ktoryTam,ssl,bio,true);
      if(wynik==-1)
      {
	 close(gniazdo);
	 goto glownapetla;
      }

      nag->rozmiar=htons((int16_t)0u);
      nag->operacja=OP_LIST;
      nag->opcje=(int8_t)0u;
      nag->ktory=htonl(ktory);
      wynik=wyslijzpotwSSL(gniazdo,&adres,bufor,bufor2,&ktory,&ktoryTam,ssl,bio,true);
      if(wynik==-1)
      {
	 close(gniazdo);
	 goto glownapetla;
      }

      fflush(stdout);
      r=fwrite(dane2,sizeof(int8_t),ntohs(ack->naglowek.rozmiar)-sizeof(uint32_t),stdout); // w dane2+sizeof(uint32_t) lista katal.
      if(r!=ntohs(ack->naglowek.rozmiar)-sizeof(uint32_t))
      {
	 fprintf(stderr,"Błąd przy pisaniu na standardowe wyjście\n");
	 close(gniazdo);
	 exit(EXIT_FAILURE);
      }
      fflush(stdout);
      wynik=fputs("\nWybierz plik: ",stdout);
      if(wynik==EOF)
      {
	 fprintf(stderr,"Nie można pisać na standardowe wyjście\n");
	 close(gniazdo);
	 exit(EXIT_FAILURE);
      }
      if(fgets(dane,sizeof(bufor)-rozmiarNaglowka,stdin)==NULL)
      {
	 fprintf(stderr,"Nie można czytać ze standardowego wejścia\n");
	 close(gniazdo);
	 exit(EXIT_FAILURE);
      }

      if(strlen(dane)==0)
      {
	 nag->rozmiar=htons((uint16_t)0u);
	 nag->operacja=OP_QUIT;
	 nag->opcje=(uint8_t)0u;
	 nag->ktory=htonl(ktory);
	 wyslijzpotwSSL(gniazdo,&adres,bufor,bufor2,&ktory,&ktoryTam,ssl,bio,true);
	 close(gniazdo);
	 exit(EXIT_SUCCESS);
      }

      /*** Tu otwierany plik - trzeba zamknąć ***/
      dane[strlen(dane)-1]='\0';
      plik=fopen(dane,"wb");
      if(plik==NULL)
      {
	 fprintf(stderr,"Nie udało się otworzyć pliku\n");
	 close(gniazdo);
	 goto glownapetla;
      }

      nag->rozmiar=htons((uint16_t)strlen(dane));
      nag->operacja=OP_GET;
      nag->opcje=(uint8_t)0u;
      nag->ktory=htonl(ktory);
      wynik=wyslijzpotwSSL(gniazdo,&adres,bufor,bufor2,&ktory,&ktoryTam,ssl,bio,true);
      if(wynik==-1)
      {
	 fclose(plik);
	 close(gniazdo);
	 goto glownapetla;
      }

      licznik=ntohl(*((uint32_t *)dane2)); //w ACK rozmiar w pakietach
#ifdef DEBUG
	 fprintf(stderr,"POCZĄTEK\n");
	 for(int zupa=0;zupa<rozmiarACK+sizeof(uint32_t);++zupa)
	 {
	    fprintf(stderr,"%02"PRIx8,bufor2[zupa]);
	 }
	 fprintf(stderr,"\nKONIEC\n");
#endif
      for(;licznik>0;--licznik)
      {
#ifdef DEBUG
	 fprintf(stderr,"Iteracja petli pobierania, licznik=%"PRIu32"\n",licznik);
#endif
	 wynik=odbierzzpotwSSL(gniazdo,&adres,bufor,&ktory,&ktoryTam,ssl,bio,true);
	 if(wynik==-1)
	 {
	    close(gniazdo);
	    fclose(plik);
	    goto glownapetla;
	 }
	 r=fwrite(dane, sizeof(int8_t), ntohs(nag->rozmiar), plik);
	 if(r!=ntohs(nag->rozmiar))
	 {
	    fprintf(stderr,"Błąd przy pisaniu na standardowe wyjście\n");
	    close(gniazdo);
	    fclose(plik);
	    goto glownapetla;
	 }
      }
      nag->rozmiar=htons((uint16_t)0u);
      nag->operacja=OP_QUIT;
      nag->opcje=(uint8_t)0u;
      nag->ktory=htonl(ktory);
      wyslijzpotwSSL(gniazdo,&adres,bufor,bufor2,&ktory,&ktoryTam,ssl,bio,true);
      close(gniazdo);
      fclose(plik);

      SSL_free(ssl);
    SSL_CTX_free(ctx);

   /*
    * -1. Wybierz serwer z listy
    * -2. Nawiąż połączenie
    * -3. Jeśli się nie uda wróć do 1
    * -4. Wyślij SET z ustawieniami transmisji
    * -5. Wyślij LIST
    * -6. Pobierz listę plików
    * -7. Wyświetl listę plików
    * -8. Wczytaj od użytkownika wybór pliku (lub CTRL-D - wtedy wyślij QUIT)
    * -9. Wyślij GET nazwa_pliku
    * 10. Wyświetl postęp pobierania pliku i błędy
    * -11. Pobierz plik
    * -12. Wyświetl informację o zakończeniu pobierania
    * -13. Idź do 4
    */
   return EXIT_SUCCESS;
}
